package com.zbkj.client.controller;

import com.alibaba.fastjson.JSON;
import com.fasterxml.jackson.core.type.TypeReference;
import com.zbkj.client.service.DigitalPersonService;
import com.zbkj.client.task.CustomDpStatusUpdateTask;
import com.zbkj.client.utils.EmojiFilterUtil;
import com.zbkj.common.model.client.agent.PackageConfig;
import com.zbkj.common.model.client.agent.WeekTask;
import com.zbkj.common.model.client.workbench.VideoTemplateFinal;
import com.zbkj.common.utils.RedisUtil;
import com.zbkj.common.result.CommonResult;
import com.zbkj.client.vo.AccessTokenResponseVo;
import com.zbkj.service.service.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.*;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.client.RestTemplate;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.HashMap;
import java.util.Map;
import java.util.List;
import java.util.ArrayList;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.zbkj.common.model.client.digital.PublicDigitalPerson;
import com.zbkj.common.model.client.digital.CustomDigitalPerson;
import java.util.Date;
import com.zbkj.client.config.ClientTokenComponent;
import com.zbkj.common.response.ClientLoginResponse;
import javax.servlet.http.HttpServletRequest;

import com.zbkj.common.model.client.digital.PublicVoice;
import com.zbkj.common.model.client.digital.CustomVoice;
import com.zbkj.common.model.client.digital.DgVideo;
import com.zbkj.client.vo.CreateDpVideoRemoteVo;
import com.zbkj.client.vo.CreateDpVideoRequestVo;
import com.zbkj.client.vo.CreateDpVoiceRemoteVo;
import com.zbkj.common.model.client.digital.DpVoice;
import com.zbkj.common.utils.OssUtil;
import java.util.concurrent.CompletableFuture;
import com.zbkj.client.task.CustomVoiceStatusUpdateTask;
import com.zbkj.client.task.DpVoiceStatusUpdateTask;

@Slf4j
@RestController
@RequestMapping("/api/client/digital-person")
public class DigitalPersonController {
    @Value("${digital-person.url}")
    private String digitalPersonUrl;
    @Value("${digital-person.app_id}")
    private String appId;
    @Value("${digital-person.secret_key}")
    private String secretKey;
    @Value("${chanjing.callback_url}")
    private String callbackUrl;

    @Autowired
    private RedisUtil redisUtil;
    @Autowired
    private RestTemplate restTemplate;
    @Autowired
    private PublicDigitalPersonService publicDigitalPersonService;
    @Autowired
    private CustomDigitalPersonService customDigitalPersonService;
    @Autowired
    private ClientTokenComponent clientTokenComponent;
    @Autowired
    private PublicVoiceService publicVoiceService;
    @Autowired
    private CustomVoiceService customVoiceService;
    @Autowired
    private DgVideoService dgVideoService;
    @Autowired
    private DpVoiceService dpVoiceService;
    @Autowired
    private CustomDpStatusUpdateTask customDpStatusUpdateTask;
    @Autowired
    private CustomVoiceStatusUpdateTask customVoiceStatusUpdateTask;
    @Autowired
    private DpVoiceStatusUpdateTask dpVoiceStatusUpdateTask;

    @Autowired
    private DigitalPersonService digitalPersonService;

    @Autowired
    private PackageConfigService packageConfigService;


    @Autowired
    private WeekTaskService weekTaskService;

//    private static final String ACCESS_TOKEN_KEY = "digital_person_access_token";
    private static final String ACCESS_TOKEN_KEY = "C:DP:TOKEN";
    private final ObjectMapper objectMapper = new ObjectMapper();

    //公共数字人列表接口
    private final String commonDpUrl = "/open/v1/list_common_dp";

    //定制数字人列表
    private final String customDpUrl = "/open/v1/list_customised_person";

    //创建定制数字人列表
    private final String customDpAddUrl = "/open/v1/create_customised_person";

    //创建定制数字人回调地址(本地地址)
    private final String createCustomDpCallback = "/dp/createCustomDp";

    //定制声音列表
    private final String customVoiceUrl = "/open/v1/list_customised_audio";

    //创建定制声音列表
    private final String customVoiceAddUrl = "/open/v1/create_customised_audio";

    //创建定制声音回调地址(本地地址)
    private final String createCustomVoiceCallback = "/dp/createCustomVoice";

    private final String dpVideoUrl = "/open/v1/video_list";
    private final String dpVideoAddUrl = "/open/v1/create_video";
    private final String createDpVideoCallback = "/dp/createDpVideo";
    private final String dpVoiceDetailUrl = "/open/v1/audio_task_state";
    private final String dpVoiceAddUrl = "/open/v1/create_audio_task";
    private final String createDpVoiceCallback = "/dp/createDpVoice";
    private final String dpVideoDelUrl = "/open/v1/delete_video";
    private final String customVoiceDelUrl = "/open/v1/delete_customised_audio";
    private final String customDpDelUrl = "/open/v1/delete_customised_person";

    private String getAccessToken() {
        String token = redisUtil.get(ACCESS_TOKEN_KEY);
        if (token != null) return token;
        String url = digitalPersonUrl + "/open/v1/access_token";
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        Map<String, String> params = new HashMap<>();
        params.put("app_id", appId);
        params.put("secret_key", secretKey);
        HttpEntity<Map<String, String>> entity = new HttpEntity<>(params, headers);
        ResponseEntity<AccessTokenResponseVo> response = restTemplate.exchange(url, HttpMethod.POST, entity, AccessTokenResponseVo.class);
        if (response.getStatusCode() == HttpStatus.OK && response.getBody() != null && response.getBody().getCode() == 0) {
            String accessToken = response.getBody().getData().getAccess_token();
            long expire = response.getBody().getData().getExpire_in();
            long now = System.currentTimeMillis() / 1000;
            redisUtil.set(ACCESS_TOKEN_KEY, accessToken, expire - now - 60); // 提前一分钟过期
            return accessToken;
        }
        throw new RuntimeException("获取access_token失败: " + (response.getBody() != null ? response.getBody().getMsg() : "未知错误"));
    }

    /**
     * 获取蝉镜的公共形象人接口
     * @param page
     * @param pageSize
     * @return
     */
    @GetMapping("/getListCommonDpRemote")
    public CommonResult<?> getListCommonDpRemote(@RequestParam(value = "page", defaultValue = "1") int page,
                                                 @RequestParam(value = "pageSize", defaultValue = "20") int pageSize) {
        String accessToken = getAccessToken();
        String url = digitalPersonUrl + commonDpUrl + "?page=" + page + "&size=" + pageSize;
        HttpHeaders headers = new HttpHeaders();
        headers.set("access_token", accessToken);
        HttpEntity<Void> entity = new HttpEntity<>(headers);
        ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.GET, entity, String.class);
        try {
            JsonNode root = objectMapper.readTree(response.getBody());
            int code = root.path("code").asInt();
            String msg = root.path("msg").asText();
            if (code == 0) {
                JsonNode listNode = root.path("data").path("list");
                if (listNode.isArray()) {
                    List<Map<String, Object>> list = objectMapper.convertValue(
                        listNode, new TypeReference<List<Map<String, Object>>>() {}
                    );
                    return CommonResult.success(list);
                } else {
                    return CommonResult.success();
                }
            } else {
                return CommonResult.failed(msg);
            }
        } catch (Exception e) {
            log.error("解析数字人列表返回异常", e);
            return CommonResult.failed("解析数字人列表返回异常");
        }
    }

    /**
     * 插入数据接口
     * @param page
     * @param pageSize
     * @return
     */
    @GetMapping("/insertDp")
    public CommonResult<?> insertDp(@RequestParam(value = "page", defaultValue = "1") int page,
                                                 @RequestParam(value = "pageSize", defaultValue = "20") int pageSize) {
        String accessToken = getAccessToken();
        String url = digitalPersonUrl + commonDpUrl + "?page=" + page + "&size=" + pageSize;
        HttpHeaders headers = new HttpHeaders();
        headers.set("access_token", accessToken);
        HttpEntity<Void> entity = new HttpEntity<>(headers);
        ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.GET, entity, String.class);
        try {
            JsonNode root = objectMapper.readTree(response.getBody());
            int code = root.path("code").asInt();
            String msg = root.path("msg").asText();
            if (code == 0) {
                JsonNode listNode = root.path("data").path("list");
                if (listNode.isArray()) {
                    List<Map<String, Object>> list = objectMapper.convertValue(
                            listNode, new TypeReference<List<Map<String, Object>>>() {}
                    );

                    //插入数据库
                    for (Map<String, Object> map : list) {
                        // 公共形象
                        PublicDigitalPerson publicDigitalPerson = new PublicDigitalPerson();
                        publicDigitalPerson.setLinkId(map.get("id").toString());
                        publicDigitalPerson.setName(map.get("name").toString());
                        publicDigitalPerson.setGender(map.get("gender").toString());
                        publicDigitalPerson.setAudioManId(map.get("audio_man_id").toString());
                        publicDigitalPerson.setAudioName(map.get("audio_name").toString());
                        publicDigitalPerson.setAudioPreview(map.get("audio_preview").toString());
                        publicDigitalPerson.setAudioLang(map.get("audio_lang").toString());

//                        ObjectMapper objectMapper = new ObjectMapper();
//                        publicDigitalPerson.setFigure(objectMapper.writeValueAsString(map.get("figures")));

                        List<Map<String, Object>> figuresObj = (List<Map<String, Object>>) map.get("figures");
                        if (figuresObj != null) {
                            String jsonString = JSON.toJSONString(figuresObj);
                            publicDigitalPerson.setFigure(jsonString);
                        } else {
                            publicDigitalPerson.setFigure(null);
                        }

                        publicDigitalPerson.setCreateTime(new Date());

                        publicDigitalPersonService.save(publicDigitalPerson);

                        //公共音色
                        PublicVoice publicVoice = new PublicVoice();
                        publicVoice.setLinkId(map.get("audio_man_id").toString());
                        publicVoice.setGender(map.get("gender").toString());
                        publicVoice.setAudioName(map.get("audio_name").toString());
                        publicVoice.setAudioPreview(map.get("audio_preview").toString());
                        publicVoice.setAudioLang(map.get("audio_lang").toString());

                        publicVoiceService.save(publicVoice);
                    }

                    return CommonResult.success(list);
                } else {
                    return CommonResult.success();
                }
            } else {
                return CommonResult.failed(msg);
            }
        } catch (Exception e) {
            log.error("解析数字人列表返回异常", e);
            return CommonResult.failed("解析数字人列表返回异常");
        }
    }

    /**
     * 获取形象人列表--mysql获取
     * @param page
     * @param pageSize
     * @return
     */
    @GetMapping("/getListCommonDp")
    public CommonResult<IPage<PublicDigitalPerson>> getListCommonDp(@RequestParam(value = "page", defaultValue = "1") int page,
                                                                   @RequestParam(value = "pageSize", defaultValue = "20") int pageSize) {
        Page<PublicDigitalPerson> pageParam = new Page<>(page, pageSize);
        IPage<PublicDigitalPerson> result = publicDigitalPersonService.page(pageParam);
        return CommonResult.success(result, "获取公共数字人形象人列表成功");
    }

    /**
     * 获取蝉镜定制数字人列表
     * @param page 页码
     * @param pageSize 每页数量
     * @return
     */
    @GetMapping("/getListCustomDpRemote")
    public CommonResult<?> getListCustomDpRemote(@RequestParam(value = "page", defaultValue = "1") int page,
                                                 @RequestParam(value = "pageSize", defaultValue = "20") int pageSize) {
        String accessToken = getAccessToken();
        String url = digitalPersonUrl + customDpUrl;
        HttpHeaders headers = new HttpHeaders();
        headers.set("access_token", accessToken);
        headers.setContentType(MediaType.APPLICATION_JSON);
        Map<String, Object> params = new HashMap<>();
        params.put("page", page);
        params.put("size", pageSize);
        HttpEntity<Map<String, Object>> entity = new HttpEntity<>(params, headers);
        ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.POST, entity, String.class);
        try {
            JsonNode root = objectMapper.readTree(response.getBody());
            int code = root.path("code").asInt();
            String msg = root.path("msg").asText();
            if (code == 0) {
                JsonNode listNode = root.path("data").path("list");
                if (listNode.isArray()) {
                    List<Map<String, Object>> list = objectMapper.convertValue(
                        listNode, new TypeReference<List<Map<String, Object>>>() {}
                    );
                    return CommonResult.success(list);
                } else {
                    return CommonResult.success();
                }
            } else {
                return CommonResult.failed(msg);
            }
        } catch (Exception e) {
            log.error("解析定制数字人列表返回异常", e);
            return CommonResult.failed("解析定制数字人列表返回异常");
        }
    }

    /**
     * 获取定制数字人列表--mysql获取
     * @param page 页码
     * @param pageSize 每页数量
     * @return
     */
    @GetMapping("/getListCustomDp")
    public CommonResult<IPage<CustomDigitalPerson>> getListCustomDp(@RequestParam(value = "page", defaultValue = "1") int page,
                                                                   @RequestParam(value = "pageSize", defaultValue = "20") int pageSize,
                                                                   HttpServletRequest httpRequest) {
        Integer userId = clientTokenComponent.getLoginUser(httpRequest);
        Page<CustomDigitalPerson> pageParam = new Page<>(page, pageSize);
        IPage<CustomDigitalPerson> result = customDigitalPersonService.lambdaQuery()
                .eq(CustomDigitalPerson::getIsDel, 0)
                .eq(CustomDigitalPerson::getUserId, userId)
                .orderByDesc(CustomDigitalPerson::getCreateTime)
                .page(pageParam);
        return CommonResult.success(result, "获取定制数字人列表成功");
    }

    /**
     * 创建定制数字人
     * @param param 包含name和materialVideo的JSON参数
     * @return
     */
    @PostMapping("/addCustomDp")
    public CommonResult<?> addCustomDp(@RequestBody Map<String, Object> param, HttpServletRequest httpRequest) {
        String name = (String) param.get("name");
        String materialVideo = (String) param.get("materialVideo");
        String authUrl = (String) param.get("authUrl");

        if (name == null || materialVideo == null) {
            return CommonResult.failed("参数不能为空");
        }
        Integer userId = clientTokenComponent.getLoginUser(httpRequest);
        String token = httpRequest.getHeader("token");
        ClientLoginResponse userInfo = clientTokenComponent.getUserInfo(userId, token);
        Integer merId = userInfo != null ? userInfo.getMerId() : null;
        String accessToken = getAccessToken();
        String url = digitalPersonUrl + customDpAddUrl;
        HttpHeaders headers = new HttpHeaders();
        headers.set("access_token", accessToken);
        headers.setContentType(MediaType.APPLICATION_JSON);
        Map<String, Object> params = new HashMap<>();
        params.put("name", name);
        params.put("material_video", materialVideo);

        //增加套餐权限判断
        PackageConfig packageConfig = packageConfigService.lambdaQuery()
                .eq(PackageConfig::getIsDel, 0)
                .eq(PackageConfig::getPackageLevel, userInfo.getPackageLevel())
                .one();

        Map<String, Map<String, Object>> configList = packageConfig.getConfigList();
        Map<String, Object> map = configList.get("video_clone");
        Integer showValue = (Integer) map.get("show_value");
        if (showValue == null || showValue == 0) {
            return CommonResult.result(10011, "您的套餐无此权限", null);
        }

        //TODO 增加训练类型参数
        params.put("train_type", "figure");
        params.put("callback", callbackUrl + createCustomDpCallback);
        HttpEntity<Map<String, Object>> entity = new HttpEntity<>(params, headers);
        ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.POST, entity, String.class);
        try {
            JsonNode root = objectMapper.readTree(response.getBody());
            int code = root.path("code").asInt();
            String msg = root.path("msg").asText();
            if (code == 0) {
                String data = root.path("data").asText();
                String traceId = root.path("trace_id").asText();
                // 插入到数据库
                CustomDigitalPerson customDigitalPerson = new CustomDigitalPerson();
                customDigitalPerson.setLinkId(data);
                customDigitalPerson.setName(name);
                customDigitalPerson.setAuthUrl(authUrl);
                customDigitalPerson.setIsOpen(0);
                customDigitalPerson.setStatus(1); // 制作中
                customDigitalPerson.setProgress(0); // 进度0
                customDigitalPerson.setIsDel(0);
                customDigitalPerson.setCreateTime(new Date());
                customDigitalPerson.setUpdateTime(new Date());
                customDigitalPerson.setUserId(userId);
                customDigitalPerson.setMerId(merId);

                customDigitalPersonService.save(customDigitalPerson);
                Map<String, Object> result = new HashMap<>();
                result.put("videoId", data);
                result.put("traceId", traceId);

                customDpStatusUpdateTask.start(data);

                return CommonResult.success(result, "创建定制数字人任务提交成功");
            } else {
                return CommonResult.failed(msg);
            }
        } catch (Exception e) {
            log.error("创建定制数字人返回异常", e);
            return CommonResult.failed("创建定制数字人返回异常");
        }
    }

    /**
     * 获取公共语音列表
     * @param page 页码
     * @param pageSize 每页数量
     * @return
     */
    @GetMapping("/getListCommonVoice")
    public CommonResult<IPage<PublicVoice>> getListCommonVoice(@RequestParam(value = "page", defaultValue = "1") int page,
                                                              @RequestParam(value = "pageSize", defaultValue = "20") int pageSize) {
        Page<PublicVoice> pageParam = new Page<>(page, pageSize);
        IPage<PublicVoice> result = publicVoiceService.page(pageParam);
        return CommonResult.success(result, "获取公共语音列表成功");
    }

    /**
     * 获取定制声音列表
     * @param page 页码
     * @param pageSize 每页数量
     * @return
     */
    @GetMapping("/getListCustomVoiceRemote")
    public CommonResult<?> getListCustomVoiceRemote(@RequestParam(value = "page", defaultValue = "1") int page,
                                                 @RequestParam(value = "pageSize", defaultValue = "20") int pageSize) {
        String accessToken = getAccessToken();
        String url = digitalPersonUrl + customVoiceUrl;
        HttpHeaders headers = new HttpHeaders();
        headers.set("access_token", accessToken);
        headers.setContentType(MediaType.APPLICATION_JSON);
        Map<String, Object> params = new HashMap<>();
        params.put("page", page);
        params.put("page_size", pageSize);
        HttpEntity<Map<String, Object>> entity = new HttpEntity<>(params, headers);
        ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.POST, entity, String.class);
        try {
            JsonNode root = objectMapper.readTree(response.getBody());
            int code = root.path("code").asInt();
            String msg = root.path("msg").asText();
            if (code == 0) {
                JsonNode listNode = root.path("data").path("list");
                if (listNode.isArray()) {
                    List<Map<String, Object>> list = objectMapper.convertValue(
                        listNode, new TypeReference<List<Map<String, Object>>>() {}
                    );
                    return CommonResult.success(list);
                } else {
                    return CommonResult.success();
                }
            } else {
                return CommonResult.failed(msg);
            }
        } catch (Exception e) {
            log.error("解析定制声音列表返回异常", e);
            return CommonResult.failed("解析定制声音列表返回异常");
        }
    }

    /**
     * 获取定制声音列表--mysql获取
     * @param page 页码
     * @param pageSize 每页数量
     * @return
     */
    @GetMapping("/getListCustomVoice")
    public CommonResult<IPage<CustomVoice>> getListCustomVoice(@RequestParam(value = "page", defaultValue = "1") int page,
                                                                   @RequestParam(value = "pageSize", defaultValue = "20") int pageSize,
                                                                   HttpServletRequest httpRequest) {
        Integer userId = clientTokenComponent.getLoginUser(httpRequest);
        log.info("getListCustomVoice - userId: {}, page: {}, pageSize: {}", userId, page, pageSize);
        
        Page<CustomVoice> pageParam = new Page<>(page, pageSize);
        
        // 先查询总数，用于调试
        long total = customVoiceService.lambdaQuery()
                .eq(CustomVoice::getIsDel, 0)
                .eq(CustomVoice::getUserId, userId)
                .count();
        log.info("getListCustomVoice - total count: {}", total);
        
        IPage<CustomVoice> result = customVoiceService.lambdaQuery()
                .eq(CustomVoice::getIsDel, 0)
                .eq(CustomVoice::getUserId, userId)
                .orderByDesc(CustomVoice::getCreateTime)
                .page(pageParam);
        
        log.info("getListCustomVoice - result total: {}, pages: {}, records: {}", 
                result.getTotal(), result.getPages(), result.getRecords().size());
        
        return CommonResult.success(result, "获取定制声音列表成功");
    }

    /**
     * 创建定制声音
     * @param param 包含name和url的JSON参数
     * @return
     */
    @PostMapping("/addCustomVoice")
    public CommonResult<?> addCustomVoice(@RequestBody Map<String, Object> param, HttpServletRequest httpRequest) {
        String name = (String) param.get("name");
        String url = (String) param.get("url");
        String headUrl = (String) param.get("headUrl");

        if (name == null || url == null) {
            return CommonResult.failed("参数不能为空");
        }
        Integer userId = clientTokenComponent.getLoginUser(httpRequest);
        String token = httpRequest.getHeader("token");
        ClientLoginResponse userInfo = clientTokenComponent.getUserInfo(userId, token);
        Integer merId = userInfo != null ? userInfo.getMerId() : null;

        //增加套餐权限判断
        PackageConfig packageConfig = packageConfigService.lambdaQuery()
                .eq(PackageConfig::getIsDel, 0)
                .eq(PackageConfig::getPackageLevel, userInfo.getPackageLevel())
                .one();

        Map<String, Map<String, Object>> configList = packageConfig.getConfigList();
        Map<String, Object> map = configList.get("voice_clone");
        Integer showValue = (Integer) map.get("show_value");
        if (showValue == null || showValue == 0) {
            return CommonResult.result(10011, "您的套餐无此权限", null);
        }

        String accessToken = getAccessToken();
        String chanjingUrl = digitalPersonUrl + customVoiceAddUrl;
        HttpHeaders headers = new HttpHeaders();
        headers.set("access_token", accessToken);
        headers.setContentType(MediaType.APPLICATION_JSON);
        Map<String, Object> params = new HashMap<>();
        params.put("name", name);
        params.put("url", url);
        params.put("text", "你好，我是你的飞梦数字音色");
        params.put("callback", callbackUrl + createCustomVoiceCallback);
        HttpEntity<Map<String, Object>> entity = new HttpEntity<>(params, headers);
        ResponseEntity<String> response = restTemplate.exchange(chanjingUrl, HttpMethod.POST, entity, String.class);
        try {
            JsonNode root = objectMapper.readTree(response.getBody());
            int code = root.path("code").asInt();
            String msg = root.path("msg").asText();
            if (code == 0) {
                String data = root.path("data").asText();
                String traceId = root.path("trace_id").asText();
                // 插入到数据库
                CustomVoice customVoice = new CustomVoice();
                customVoice.setLinkId(data);
                customVoice.setName(name);
                customVoice.setHeadUrl(headUrl);
                customVoice.setUserId(userId);
                customVoice.setMerId(merId);
//                customVoice.setAudioPath(url);
//                customVoice.setOssAudioPath(url);
                customVoice.setStatus(1); // 制作中
                customVoice.setIsDel(0);
                customVoice.setCreateTime(new Date());
                customVoice.setUpdateTime(new Date());
                customVoiceService.save(customVoice);
                Map<String, Object> result = new HashMap<>();
                result.put("voiceId", data);
                result.put("traceId", traceId);

                // 启动定时任务，1分钟后自动拉取定制声音状态
                customVoiceStatusUpdateTask.start(data);

                return CommonResult.success(result, "创建定制声音任务提交成功");
            } else {
                return CommonResult.failed(msg);
            }
        } catch (Exception e) {
            log.error("创建定制声音返回异常", e);
            return CommonResult.failed("创建定制声音返回异常");
        }
    }

    /**
     * 获取数字人视频列表
     * @param page 默认1
     * @param pageSize 默认20
     * @param httpRequest HTTP请求对象
     * @return
     */
    @GetMapping("/getListDpVideo")
    public CommonResult<IPage<DgVideo>> getListDpVideo(@RequestParam(value = "page", defaultValue = "1") int page,
                                                      @RequestParam(value = "pageSize", defaultValue = "20") int pageSize,
                                                      HttpServletRequest httpRequest) {
        Integer userId = clientTokenComponent.getLoginUser(httpRequest);
        Page<DgVideo> pageParam = new Page<>(page, pageSize);
        IPage<DgVideo> result = dgVideoService.lambdaQuery()
                .eq(DgVideo::getIsDel, 0)
                .eq(DgVideo::getUserId, userId)
                .orderByDesc(DgVideo::getCreateTime)
                .page(pageParam);
        return CommonResult.success(result, "获取合成数字人视频列表成功");
    }

    /**
     * 获取数字人视频列表-数据源为蝉镜
     * @param page 页码
     * @param pageSize 每页数量
     * @return
     */
    @GetMapping("/getListDpVideoRemote")
    public CommonResult<?> getListDpVideoRemote(@RequestParam(value = "page", defaultValue = "1") int page,
                                               @RequestParam(value = "pageSize", defaultValue = "20") int pageSize) {
        String accessToken = getAccessToken();
        String url = digitalPersonUrl + dpVideoUrl;
        HttpHeaders headers = new HttpHeaders();
        headers.set("access_token", accessToken);
        headers.setContentType(MediaType.APPLICATION_JSON);
        Map<String, Object> params = new HashMap<>();
        params.put("page", page);
        params.put("page_size", pageSize);
        HttpEntity<Map<String, Object>> entity = new HttpEntity<>(params, headers);
        ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.POST, entity, String.class);
        try {
            JsonNode root = objectMapper.readTree(response.getBody());
            int code = root.path("code").asInt();
            String msg = root.path("msg").asText();
            if (code == 0) {
                JsonNode listNode = root.path("data").path("list");
                if (listNode.isArray()) {
                    List<Map<String, Object>> list = objectMapper.convertValue(
                        listNode, new TypeReference<List<Map<String, Object>>>() {}
                    );
                    return CommonResult.success(list);
                } else {
                    return CommonResult.success();
                }
            } else {
                return CommonResult.failed(msg);
            }
        } catch (Exception e) {
            log.error("解析数字人视频列表返回异常", e);
            return CommonResult.failed("解析数字人视频列表返回异常");
        }
    }

    /**
     *  合成数字人视频
     * @param requestVo
     * @param httpRequest
     * @return
     */
    @PostMapping("/addDpVideo")
    public CommonResult<?> addDpVideo(@RequestBody CreateDpVideoRequestVo requestVo, HttpServletRequest httpRequest) {
        String contentFilterEmoji = EmojiFilterUtil.removeEmojis(requestVo.getText());
        requestVo.setText(contentFilterEmoji);

        Integer userId = clientTokenComponent.getLoginUser(httpRequest);
        String token = httpRequest.getHeader("token");
        ClientLoginResponse userInfo = clientTokenComponent.getUserInfo(userId, token);
        Integer merId = userInfo != null ? userInfo.getMerId() : null;
        String accessToken = getAccessToken();
        String url = digitalPersonUrl + dpVideoAddUrl;
        HttpHeaders headers = new HttpHeaders();
        headers.set("access_token", accessToken);
        headers.setContentType(MediaType.APPLICATION_JSON);

        // 构建请求参数
        Map<String, Object> params = new HashMap<>();

        //增加背景颜色
        params.put("bg_color", "#F0F0F0");
        
        // 构建person对象
        Map<String, Object> person = new HashMap<>();
        person.put("id", requestVo.getLinkId()); // linkId
        person.put("x", 0);
//        person.put("y", 480);

        Integer width = 1080;
        Integer height = 1920;

        person.put("width", width);


        //公式计算y的值，合成视频时用了1080(w_c) × 1920(h_c)
        //数字人视频的尺寸368(w_k) × 640(h_k) ---需要从表中取数字人的宽和高
        //y = (h_c - (w_c / w_k * h_k)) * 0.5
        //y = (height - (width/dpWidth * dpHight)) * 0.5   (1920-(1080/368 * 640)) * 0.5

        Integer dpWidth = 1080;
        Integer dpHight = 1920;

        if (requestVo.getIsCommonDp() != null && requestVo.getIsCommonDp() == 1) {
            // 根据isCommonDp判断是否设置figure_type
            person.put("figure_type", "whole_body");
        }else{
            //如果是定制数字人，则从eb_client_custom_digital_person 表中取结果
            CustomDigitalPerson customDigitalPerson = customDigitalPersonService.lambdaQuery()
                    .eq(CustomDigitalPerson::getLinkId, requestVo.getLinkId())
                    .one();

            dpWidth = customDigitalPerson.getWidth();
            dpHight = customDigitalPerson.getHeight();
        }

        Integer temp = (int) Math.round((height - ((double)width/dpWidth * dpHight)) * 0.5);
        Integer y = (int) Math.round(temp);

        Integer heightRes = (int) Math.round((double)width/dpWidth * dpHight);
        if (heightRes % 2 != 0) {
            heightRes = heightRes + 1;
        }

        person.put("y", y);
        person.put("height", heightRes);

        
        // 根据isCommonDp判断是否设置figure_type
        if (requestVo.getIsCommonDp() != null && requestVo.getIsCommonDp() == 1) {
            person.put("figure_type", "whole_body");
        }
        params.put("person", person);

        // 构建字幕显示字段
//        Map<String, Object> subtitleConfig = new HashMap<>();
//        subtitleConfig.put("show", true);
//
//        params.put("subtitle_config", subtitleConfig);

        
        // 构建audio对象
        Map<String, Object> audio = new HashMap<>();
        audio.put("type", requestVo.getType());
        audio.put("volume", 100);
        
        // 根据type判断是否设置tts或wav_url
        if ("tts".equalsIgnoreCase(requestVo.getType()) && requestVo.getText() != null) {
            Map<String, Object> tts = new HashMap<>();
            // 将text改为数组形式
            List<String> textArray = new ArrayList<>();
            textArray.add(requestVo.getText());
            tts.put("text", textArray);
            // 如果type=tts，则添加audio_man参数
            if (requestVo.getAudioManId() != null && !requestVo.getAudioManId().isEmpty()) {
                tts.put("audio_man", requestVo.getAudioManId());
                tts.put("speed", 1.1);
//                tts.put("pitch", 1);
            }
            audio.put("tts", tts);
        }
        
        if ("audio".equalsIgnoreCase(requestVo.getType()) && requestVo.getWavUrl() != null && !requestVo.getWavUrl().isEmpty()) {
            audio.put("wav_url", requestVo.getWavUrl());
        }
        params.put("audio", audio);
        
        // 设置背景
        if (requestVo.getBgUrl() != null && !requestVo.getBgUrl().isEmpty()) {
            Map<String, Object> bg = new HashMap<>();
            bg.put("src_url", requestVo.getBgUrl());
            params.put("bg", bg);
        }

        log.info("传给蝉镜的参数：{}", params);

        String name = requestVo.getName();

        Map<String, Object> saveLocationMap = new HashMap<>();
        saveLocationMap.put("module", requestVo.getSaveModule());
        saveLocationMap.put("parent_id", requestVo.getSaveParentId());

        DgVideo dgVideo = digitalPersonService.insertDpVideo(userId, merId, name, saveLocationMap, requestVo);
        Integer id = dgVideo.getId();



        CompletableFuture<Integer> integerCompletableFuture = digitalPersonService.addDpVideoCommon(dgVideo, params, requestVo.getType(), requestVo.getAudioManId(), requestVo.getText(), requestVo.getVoiceId(), userId, merId, name, saveLocationMap);

        Map<String, Object> result = new HashMap<>();
        result.put("id", id);

        return CommonResult.success(result, "合成数字人视频任务提交成功");
    }

    /**
     * 获取蝉镜合成声音接口
     * @param linkId 任务ID
     * @return
     */
    @GetMapping("/getDpVoiceDetail")
    public CommonResult<?> getDpVoiceDetail(@RequestParam (required = false) String linkId, @RequestParam (required = false) Integer id) {

        DpVoice dpVoice = null;
        if (linkId != null && !linkId.isEmpty()) {
            dpVoice = dpVoiceService.lambdaQuery()
                    .eq(DpVoice::getLinkId, linkId)
                    .one();
        }else if (id != null) {
            dpVoice = dpVoiceService.getById(id);
        }

        
        if (dpVoice != null) {
            return CommonResult.success(dpVoice, "获取合成声音详情成功");
        } else {
            return CommonResult.failed("未找到对应的语音记录");
        }
    }

    /**
     * 获取合成声音列表（本地数据库）
     * @param page 页码，默认1
     * @param pageSize 每页数量，默认20
     * @param httpRequest HTTP请求对象
     * @return 分页结果
     */
    @GetMapping("/getListDpVoice")
    public CommonResult<IPage<DpVoice>> getListDpVoice(@RequestParam(value = "page", defaultValue = "1") int page,
                                                      @RequestParam(value = "pageSize", defaultValue = "20") int pageSize,
                                                      HttpServletRequest httpRequest) {
        Integer userId = clientTokenComponent.getLoginUser(httpRequest);
        Page<DpVoice> pageParam = new Page<>(page, pageSize);
        IPage<DpVoice> result = dpVoiceService.lambdaQuery()
                .eq(DpVoice::getIsDel, 0)
                .eq(DpVoice::getUserId, userId)
                .orderByDesc(DpVoice::getCreateTime)
                .page(pageParam);
        return CommonResult.success(result, "获取合成声音列表成功");
    }

    /**
     * 创建语音任务
     * @param requestVo 请求参数
     * @param httpRequest HTTP请求对象
     * @return
     */
    @PostMapping("/addDpVoice")
    public CommonResult<?> addDpVoice(@RequestBody CreateDpVoiceRemoteVo requestVo, HttpServletRequest httpRequest) {
        // 验证文本长度
        if (requestVo.getText() != null && requestVo.getText() != null) {
            String text = requestVo.getText();
            if (text.length() > 4000) {
                return CommonResult.failed("文本长度不能超过4000字");
            }
        }
        
        Integer userId = clientTokenComponent.getLoginUser(httpRequest);
        String token = httpRequest.getHeader("token");
        ClientLoginResponse userInfo = clientTokenComponent.getUserInfo(userId, token);
        Integer merId = userInfo != null ? userInfo.getMerId() : null;

        //TODO 后面可以把构建参数独立成一个方法，方便其他方法调用
        // 构建请求参数
        Map<String, Object> params = new HashMap<>();
        params.put("audio_man", requestVo.getLinkId());
        params.put("speed", 1.1);
        params.put("pitch", 1);

        // 设置文本内容
        if (requestVo.getText() != null && requestVo.getText() != null) {
            Map<String, Object> textMap = new HashMap<>();
            textMap.put("text", requestVo.getText());
            textMap.put("plain_text", requestVo.getText());
            params.put("text", textMap);
        }
        // 设置回调地址
        params.put("callback", callbackUrl + createDpVoiceCallback);

        Map<String, Object> res = digitalPersonService.addDpVoice(params, userId, merId);
        if (res.get("code").equals(0)){
            //合成声音 定时任务开启
//            dpVoiceStatusUpdateTask.start((String) res.get("voiceId"));
            Map<String, Object> map = new HashMap<>();
            map.put("traceId", (String) res.get("traceId"));
            map.put("voiceId", (String)res.get("voiceId"));
            map.put("id", (Integer)res.get("id"));
//            return CommonResult.success(map);
            return CommonResult.success(map, "合成声音任务提交成功");
        }else {
            return CommonResult.failed((String) res.get("msg"));
        }
    }

    /**
     * 获取蝉镜合成声音接口
     * @param linkId 任务ID
     * @return
     */
    @GetMapping("/getDpVoiceDetailRemote")
    public CommonResult<?> getDpVoiceDetailRemote(@RequestParam String linkId) {
        String accessToken = getAccessToken();
        String url = digitalPersonUrl + dpVoiceDetailUrl;
        HttpHeaders headers = new HttpHeaders();
        headers.set("access_token", accessToken);
        headers.setContentType(MediaType.APPLICATION_JSON);
        
        Map<String, Object> params = new HashMap<>();
        params.put("task_id", linkId);
        
        HttpEntity<Map<String, Object>> entity = new HttpEntity<>(params, headers);
        ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.POST, entity, String.class);
        
        try {
            JsonNode root = objectMapper.readTree(response.getBody());
            int code = root.path("code").asInt();
            String msg = root.path("msg").asText();
            
            if (code == 0) {
                JsonNode data = root.path("data");
                return CommonResult.success(data);
            } else {
                return CommonResult.failed(msg);
            }
        } catch (Exception e) {
            log.error("获取蝉镜合成声音接口返回异常", e);
            return CommonResult.failed("获取蝉镜合成声音接口返回异常");
        }
    }

    /**
     * 获取合成视频详情
     * @param id 视频主键ID
     * @return
     */
    @GetMapping("/getDpVideoDetail")
    public CommonResult<?> getDpVideoDetail(@RequestParam Integer id) {
        DgVideo dgVideo = dgVideoService.getById(id);
        return CommonResult.success(dgVideo, "获取合成数字人视频详情成功");
    }

    /**
     * 删除合成声音
     * @param param 包含id的JSON参数
     * @return 删除结果
     */
    @PostMapping("/delDpVoice")
    public CommonResult<?> delDpVoice(@RequestBody Map<String, Object> param) {
        try {
            // 从JSON参数中获取id
            Integer id = (Integer) param.get("id");
            if (id == null) {
                return CommonResult.failed("参数id不能为空");
            }
            
            // 根据id查询记录是否存在
            DpVoice dpVoice = dpVoiceService.getById(id);
            if (dpVoice == null || dpVoice.getIsDel() == 1) {
                return CommonResult.failed("记录不存在");
            }
            
            // 软删除记录
            dpVoice.setIsDel(1);
            dpVoice.setUpdateTime(new Date());
            boolean updateResult = dpVoiceService.updateById(dpVoice);
            
            if (!updateResult) {
                return CommonResult.failed("删除失败");
            }
            
            // 异步删除OSS资源
            String ossUrl = dpVoice.getOssUrl();
            if (ossUrl != null && !ossUrl.isEmpty()) {
                // 使用异步任务删除OSS文件
                CompletableFuture.runAsync(() -> {
                    try {
                        OssUtil.deleteFileFromUrl(ossUrl);
                        log.info("异步删除OSS文件成功: {}", ossUrl);
                    } catch (Exception e) {
                        log.error("异步删除OSS文件失败: {}, 错误信息: {}", ossUrl, e.getMessage());
                    }
                });
            }
            
            return CommonResult.success("删除成功", "删除合成声音成功");
            
        } catch (Exception e) {
            log.error("删除合成声音异常", e);
            return CommonResult.failed("删除失败: " + e.getMessage());
        }
    }

    /**
     * 删除合成视频
     * @param param 包含id的JSON参数
     * @return 删除结果
     */
    @PostMapping("/delDpVideo")
    public CommonResult<?> delDpVideo(@RequestBody Map<String, Object> param) {
        try {
            // 从JSON参数中获取id
            Integer id = (Integer) param.get("id");
            if (id == null) {
                return CommonResult.failed("参数id不能为空");
            }
            
            // 根据id查询记录是否存在
            DgVideo dgVideo = dgVideoService.getById(id);
            if (dgVideo == null || dgVideo.getIsDel() == 1) {
                return CommonResult.failed("记录不存在");
            }
            
            // 软删除记录
            dgVideo.setIsDel(1);
            dgVideo.setUpdateTime(new Date());
            boolean updateResult = dgVideoService.updateById(dgVideo);
            
            if (!updateResult) {
                return CommonResult.failed("删除失败");
            }
            
            // 收集需要删除的OSS文件URL
            List<String> ossUrls = new ArrayList<>();
            
            // 判断oss_video_url是否为空，不为空则添加到列表
            if (dgVideo.getOssVideoUrl() != null && !dgVideo.getOssVideoUrl().isEmpty()) {
                ossUrls.add(dgVideo.getOssVideoUrl());
            }
            
            // 判断oss_preview_url是否为空，不为空则添加到列表
            if (dgVideo.getOssPreviewUrl() != null && !dgVideo.getOssPreviewUrl().isEmpty()) {
                ossUrls.add(dgVideo.getOssPreviewUrl());
            }
            
            // 判断oss_subtitle_data_url是否为空，不为空则添加到列表
            if (dgVideo.getOssSubtitleDataUrl() != null && !dgVideo.getOssSubtitleDataUrl().isEmpty()) {
                ossUrls.add(dgVideo.getOssSubtitleDataUrl());
            }
            
            // 如果列表不为空，异步删除OSS文件
            if (!ossUrls.isEmpty()) {
                CompletableFuture.runAsync(() -> {
                    try {
                        OssUtil.deleteMultiFileFromUrls(ossUrls);
                        log.info("异步删除OSS文件成功: {}", ossUrls);
                    } catch (Exception e) {
                        log.error("异步删除OSS文件失败: {}, 错误信息: {}", ossUrls, e.getMessage());
                    }
                });
            }
            
            // 如果link_id存在，异步请求蝉镜删除视频
            String linkId = dgVideo.getLinkId();
            if (linkId != null && !linkId.isEmpty()) {
                CompletableFuture.runAsync(() -> {
                    try {
                        String accessToken = getAccessToken();
                        String url = digitalPersonUrl + dpVideoDelUrl;
                        HttpHeaders headers = new HttpHeaders();
                        headers.set("access_token", accessToken);
                        headers.setContentType(MediaType.APPLICATION_JSON);
                        
                        // 构建JSON请求体
                        Map<String, Object> requestBody = new HashMap<>();
                        requestBody.put("id", linkId);
                        
                        HttpEntity<Map<String, Object>> entity = new HttpEntity<>(requestBody, headers);
                        ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.POST, entity, String.class);
                        
                        log.info("蝉镜删除视频请求成功: linkId={}", linkId);
                    } catch (Exception e) {
                        log.error("蝉镜删除视频请求失败: linkId={}, 错误信息: {}", linkId, e.getMessage());
                    }
                });
            }
            
            return CommonResult.success("删除成功", "删除合成视频成功");
            
        } catch (Exception e) {
            log.error("删除合成视频异常", e);
            return CommonResult.failed("删除失败: " + e.getMessage());
        }
    }

    /**
     * 删除定制声音
     * @param param 包含id的JSON参数
     * @return 删除结果
     */
    @PostMapping("/delCustomVoice")
    public CommonResult<?> delCustomVoice(@RequestBody Map<String, Object> param) {
        try {
            // 从JSON参数中获取id
            Integer id = (Integer) param.get("id");
            if (id == null) {
                return CommonResult.failed("参数id不能为空");
            }
            
            // 根据id查询记录是否存在
            CustomVoice customVoice = customVoiceService.getById(id);
            if (customVoice == null || customVoice.getIsDel() == 1) {
                return CommonResult.failed("记录不存在");
            }
            
            // 软删除记录
            customVoice.setIsDel(1);
            customVoice.setUpdateTime(new Date());
            boolean updateResult = customVoiceService.updateById(customVoice);
            
            if (!updateResult) {
                return CommonResult.failed("删除失败");
            }
            
            // 异步删除OSS资源
            String ossAudioPath = customVoice.getOssAudioPath();
            if (ossAudioPath != null && !ossAudioPath.isEmpty()) {
                CompletableFuture.runAsync(() -> {
                    try {
                        OssUtil.deleteFileFromUrl(ossAudioPath);
                        log.info("异步删除OSS文件成功: {}", ossAudioPath);
                    } catch (Exception e) {
                        log.error("异步删除OSS文件失败: {}, 错误信息: {}", ossAudioPath, e.getMessage());
                    }
                });
            }
            
            // 如果link_id存在，异步请求蝉镜删除定制声音
            String linkId = customVoice.getLinkId();
            if (linkId != null && !linkId.isEmpty()) {
                CompletableFuture.runAsync(() -> {
                    try {
                        String accessToken = getAccessToken();
                        String url = digitalPersonUrl + customVoiceDelUrl;
                        HttpHeaders headers = new HttpHeaders();
                        headers.set("access_token", accessToken);
                        headers.setContentType(MediaType.APPLICATION_JSON);
                        
                        // 构建JSON请求体
                        Map<String, Object> requestBody = new HashMap<>();
                        requestBody.put("id", linkId);
                        
                        HttpEntity<Map<String, Object>> entity = new HttpEntity<>(requestBody, headers);
                        ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.POST, entity, String.class);
                        
                        log.info("蝉镜删除定制声音请求成功: linkId={}", linkId);
                    } catch (Exception e) {
                        log.error("蝉镜删除定制声音请求失败: linkId={}, 错误信息: {}", linkId, e.getMessage());
                    }
                });
            }
            
            return CommonResult.success("删除成功", "删除定制声音成功");
            
        } catch (Exception e) {
            log.error("删除定制声音异常", e);
            return CommonResult.failed("删除失败: " + e.getMessage());
        }
    }

    /**
     * 删除定制数字人
     * @param param 包含id的JSON参数
     * @return 删除结果
     */
    @PostMapping("/delCustomDp")
    public CommonResult<?> delCustomDp(@RequestBody Map<String, Object> param) {
        try {
            // 从JSON参数中获取id
            Integer id = (Integer) param.get("id");
            if (id == null) {
                return CommonResult.failed("参数id不能为空");
            }
            
            // 根据id查询记录是否存在
            CustomDigitalPerson customDigitalPerson = customDigitalPersonService.getById(id);
            if (customDigitalPerson == null || customDigitalPerson.getIsDel() == 1) {
                return CommonResult.failed("记录不存在");
            }
            
            // 软删除记录
            customDigitalPerson.setIsDel(1);
            customDigitalPerson.setUpdateTime(new Date());
            boolean updateResult = customDigitalPersonService.updateById(customDigitalPerson);
            
            if (!updateResult) {
                return CommonResult.failed("删除失败");
            }
            
            // 收集需要删除的OSS文件URL
            List<String> ossUrls = new ArrayList<>();
            
            // 判断oss_pic_url是否为空，不为空则添加到列表
            if (customDigitalPerson.getOssPicUrl() != null && !customDigitalPerson.getOssPicUrl().isEmpty()) {
                ossUrls.add(customDigitalPerson.getOssPicUrl());
            }
            
            // 判断oss_preview_url是否为空，不为空则添加到列表
            if (customDigitalPerson.getOssPreviewUrl() != null && !customDigitalPerson.getOssPreviewUrl().isEmpty()) {
                ossUrls.add(customDigitalPerson.getOssPreviewUrl());
            }
            
            // 如果列表不为空，异步删除OSS文件
            if (!ossUrls.isEmpty()) {
                CompletableFuture.runAsync(() -> {
                    try {
                        OssUtil.deleteMultiFileFromUrls(ossUrls);
                        log.info("异步删除OSS文件成功: {}", ossUrls);
                    } catch (Exception e) {
                        log.error("异步删除OSS文件失败: {}, 错误信息: {}", ossUrls, e.getMessage());
                    }
                });
            }
            
            // 如果link_id存在，异步请求蝉镜删除定制数字人
            String linkId = customDigitalPerson.getLinkId();
            if (linkId != null && !linkId.isEmpty()) {
                CompletableFuture.runAsync(() -> {
                    try {
                        String accessToken = getAccessToken();
                        String url = digitalPersonUrl + customDpDelUrl;
                        HttpHeaders headers = new HttpHeaders();
                        headers.set("access_token", accessToken);
                        headers.setContentType(MediaType.APPLICATION_JSON);
                        
                        // 构建JSON请求体
                        Map<String, Object> requestBody = new HashMap<>();
                        requestBody.put("id", linkId);
                        
                        HttpEntity<Map<String, Object>> entity = new HttpEntity<>(requestBody, headers);
                        ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.POST, entity, String.class);
                        
                        log.info("蝉镜删除定制数字人请求成功: linkId={}", linkId);
                    } catch (Exception e) {
                        log.error("蝉镜删除定制数字人请求失败: linkId={}, 错误信息: {}", linkId, e.getMessage());
                    }
                });
            }
            
            return CommonResult.success("删除成功", "删除定制数字人成功");
            
        } catch (Exception e) {
            log.error("删除定制数字人异常", e);
            return CommonResult.failed("删除失败: " + e.getMessage());
        }
    }
} 